package fr.umlv.ig.model;

import java.awt.Point;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.geom.Point2D;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.SortedMap;
import java.util.TreeMap;

import javax.swing.Timer;

import fr.umlv.ig.event.LineEvent;
import fr.umlv.ig.event.StationEvent;
import fr.umlv.ig.event.TrainEvent;

/**
 * Implements the {@link MetroModel} and used methods in AbstractMetroModel.
 * @author jenseric
 *
 */
public class MetroModelImpl extends AbstractMetroModel {
	/**List of line presents in the model.*/
	private final List<Line> lines;
	private final TreeMap<String, Station> stations = new TreeMap<String, Station>();
	
	ActionListener moveTrains = new ActionListener () {
		@Override
		public void actionPerformed(ActionEvent e) {		
			moveTrains();
		}
	};
	public final Timer timer = new Timer(20, moveTrains);

	
	
	/***/
	private final Random generator = new Random();
	
	/**
	 * Initializes a {@link MetroModelImpl} empty.
	 */
	public MetroModelImpl() {
		lines = new ArrayList<Line>();
	}

	
	@Override
	public int getCountLine() {
		return lines.size();
	}
	
	@Override
	public Line getLine(int index) {
		return lines.get(index);
	}
	
	/**
	 * Unselects an object presents in the model.
	 * @param object the object to unselect.
	 */
	public void unselected(Object object) {
		if(object instanceof Line) {
			unselectedLine((Line)object);
		}
		else if(object instanceof Station) {
			unselectedStation((Station)object);
		}
		else if(object instanceof Train) {
			unselectedTrain((Train)object);
		}

	}

	/**
	 * Selects an object presents in the model.
	 * @param object the object to select.
	 */
	public void selected(Object object) {
		if(object instanceof Line) {
			selectedLine((Line)object);
		}
		else if(object instanceof Station) {
			selectedStation((Station)object);
		}
		else if(object instanceof Train) {
			selectedTrain((Train)object);
		}
		
	}

	
////////////////////////////////////////////////////////////////////////////////
//////////////////////////////Methods of LineModel//////////////////////////////	
////////////////////////////////////////////////////////////////////////////////	


	@Override
	public int getCountStation(Line line) {
		return line.getCountStation();
	}

	
	@Override
	public int getCountTrain(Line line) {
		return  line.getCountTrain();
	}
	

	@Override
	public int getCountItinerary(Line line) {
		return line.getCountItinerary();
	}

	
	@Override
	public Station getStation(Line line, int index) {
		return line.getStation(index);
	}
	
	
	@Override
	public Train getTrain(Line line,int index) {
		return line.getTrain(index);
	}
	
	
	@Override
	public Itinerary getItinerary(Line line, int index) {
		return line.getItinerary(index);
	}
	
	
	/**
	 * Adds Line to the model.
	 * @param line the line to add.
	 * @throws IllegalArgumentException
	 *         The line is already in the model.
	 * @throws NullPointerException
	 *         The line is null.
	 */
	public void addLine(Line line) {
		if(line==null) {
			throw new NullPointerException("The line is null");
		}
		if(lines.contains(line)) {
			throw new IllegalArgumentException("The Line is already in the model.");
		}
		lines.add(line);
		fireLineChanged(new LineEvent(line,LineEvent.LINE_ADDED));
	}
	
	
	/**
	 * Selects a line.
	 * @param line the line to selected. 
	 */
	public void selectedLine(Line line) {
		line.setSelected(true);
		fireLineChanged(new LineEvent(line, LineEvent.LINE_SELECTED));
	}
	
	/**
	 * Unselects a line.
	 * @param line the line to unselect.
	 */
	public void unselectedLine(Line line) {
		line.setSelected(false);
		fireLineChanged(new LineEvent(line, LineEvent.LINE_UNSELECTED));
	}
	
	

	@Override
	public boolean isSelected(Line line) {
		return line.isSelected();
	}

	

	
////////////////////////////////////////////////////////////////////////////////
//////////////////////////////Methods of StationModel///////////////////////////
////////////////////////////////////////////////////////////////////////////////

	
	
	@Override
	public boolean isOpened(Station station) {
		return station.isOpened();
	}
	
	@Override
	public Point getCoordinate(Station station) {
		return station.getCoordinate();
	}


	@Override
	public int getX(Station station) {
		return station.getX();
	}


	@Override
	public int getY(Station station) {
		return station.getY();
	}
	
	@Override
	public int getWaitingTime(Station station) {
		return station.getWaitingTime();
	}

	@Override
	public int getWaitingTime(Station station, Station nextStation) {
		return station.getWaitingTime(nextStation);
	}

	
	@Override
	public Station getNextStation(Station station, Station direction) {
		return station.getNextStation(direction);
	}
	
	public Line getLine(Station station) {
		return station.getLine();
	}


	@Override
	public Map<Station,Station> getMapNextStations(Station station) {
		return station.getMapNextStations();
	}
	
	
	@Override
	public boolean isSelected(Station station) {
		return station.isSelected();
	}
	
	/**
	 * Adds Station to the model
	 * @param line the line where add the station.
	 * @param station the station to add.
	 */
	public void addStation(Line line, Station station) {
		if(line == null) {
			throw new NullPointerException("the line is null");
		}
		line.addStation(station);
		stations.put(station.getName().toUpperCase(), station);
		fireStationChanged(new StationEvent(station, StationEvent.STATION_CREATE));
	}
	
	/**
	 * Adds next station to a station.
	 * @param line the line where we add the relation.
	 * @param direction the direction of relation added.
	 * @param station the station which is changing.
	 * @param nextStation the station which will be connected by the other station.
	 * @throws NullPointerException 
	 *         The line is null
	 * @throws IllegalArgumentException
	 *         Stations are not in the line.
	 */
	public void addNextStation(Line line, Station direction, Station station, Station nextStation) {
		if(line == null) {
			throw new NullPointerException("the Line is null");
		}
		if(!line.contains(direction) || !line.contains(station) || !line.contains(nextStation)) {
			throw new IllegalArgumentException("stations are not in the Line");
		}
		station.addNextStation(direction, nextStation);
		fireStationChanged(new StationEvent(station, StationEvent.STATION_ADDNEXT),nextStation,direction);
	}	
	
	/**
	 * Opens a station.
	 * @param station the station to open.
	 */
	public void openStation(Station station ) {
		if(station == null || station.isOpened()) {
			return;
		}
		station.setOpened(true);
		fireStationChanged(new StationEvent(station, StationEvent.STATION_OPENED));
	}
	
	/**
	 * Sets the waiting time of a station.
	 * @param station the station to change.
	 * @param waitingTime the waitingTime setted.
	 */
	public void setWaitingTime(Station station, int waitingTime) {
		if(station.getWaitingTime() == waitingTime) {
			return;
		}
		station.setWaitingTime(waitingTime);
		fireStationChanged(new StationEvent(station, StationEvent.CHANGED_WAITING));
	}
	
	/**
	 * Sets the waiting time between two station.
	 * @param station the station to changed.
	 * @param nextStation the nextStation. 
	 * @param waitingTime the waiting time set.
	 */
	public void setWaitingTime(Station station,Station nextStation, int waitingTime) {
		station.setWaitingTime(nextStation, waitingTime);
		fireStationChanged(new StationEvent(station, StationEvent.CHNAGED_NEXT_WAITING), nextStation);
	}
	
	
	/**
	 * Selects a station.
	 * @param station the station to select.
	 */
	public void selectedStation(Station station) {
		station.setSelected(true);
		fireStationChanged(new StationEvent(station, StationEvent.STATION_SELECTED));
	}
	
	/**
	 * Unselect a station.
	 * @param station the station to select.
	 */
	public void unselectedStation(Station station) {
		station.setSelected(false);
		fireStationChanged(new StationEvent(station, StationEvent.STATION_UNSELECTED));
	}
	

	
	
////////////////////////////////////////////////////////////////////////////////
//////////////////////////////Methods of TrainModel/////////////////////////////
////////////////////////////////////////////////////////////////////////////////
	@Override
	public Itinerary getItinerary(Train train) {
		return train.getItinerary();
	}


	@Override
	public Station getCurrentStation(Train train) {
		return train.getCurrentStation();
	}


	@Override
	public Station getNextStation(Train train) {
		return train.getNextStation();
	}


	@Override
	public Point2D getCoordinate(Train train) {
		return train.getCoordinate();
	}


	@Override
	public Line getLine(Train train) {
		return train.getLine();
	}
	
	@Override
	public Station getTerminus(Train train) {
		return train.getTerminus();
	}
	
	
	@Override
	public boolean isMoving(Train train) {
		return train.isMoving();
	}
	
	
	@Override
	public boolean isSelected(Train train) {
		return train.isSelected();
	}
	
	/**
	 * Closes a station
	 * @param station the station to cloe.
	 */
	public void closeStation(Station station ) {
		
		if(station == null || !station.isOpened()) {
			return;
		}
		station.setOpened(false);	
		fireStationChanged(new StationEvent(station, StationEvent.STATION_CLOSED));
	}

	
	/**
	 * Adds trains to a line
	 * @param line the line tp 
	 * @param nbTrains
	 */
	public void addTrains(Line line, int nbTrains) {
		
		if(line.getCountTrain() < nbTrains) {
			while(line.getCountTrain() < nbTrains) {
				Itinerary itinerary = line.getItinerary(generator.nextInt(line.getCountItinerary()));
				Train train = new Train(itinerary);
				addTrain(line, train);
			}
		}
		else if(line.getCountTrain() > nbTrains) {
			while(line.getCountTrain() > nbTrains) {
				//removeTrain(line, train);
			}
		}
		
	}
	
	/**
	 * Adds train in a line.
	 * @param line the line where the train is added.
	 * @param train the train to add.
	 */
	public void addTrain(Line line, Train train) {
		if(line == null ) {
			throw new NullPointerException("The line is null");
		}
		if(!line.contains(train.getItinerary())) {
			return;
		}
		if(train.getCurrentStation().getState(train.getTerminus())) {
			return;
		}
		line.addTrain(train);
		fireTrainChanged(new TrainEvent(train,TrainEvent.TRAIN_ADDED));
	}

	/**
	 * Remove a Train from the model.
	 * @param train the train to remove.
	 */
	public void removeTrain(Train train) {
		if(train == null ) {
			return ;
		}
		if(!train.getLine().getTrains().contains(train)) {
			return;
		}
		train.getLine().removeTrain(train);
		fireTrainChanged(new TrainEvent(train,TrainEvent.TRAIN_REMOVE));
	}

	
	/**
	 * Moves all the train of the model.
	 */
	public void moveTrains() {
		for(Line line : lines) {
			for(Train train : line.getTrains()) {
				Station nextStation = train.getNextStation();

				if(nextStation.equals(train.getTerminus())) {
					train.move();
				}
				else if (train.isMoving() && nextStation.getState(train.getTerminus())) {
					//do nothing
				}
				else {
					train.move();
				}
		
			}
		}
		fireAllTrain();
	}


	/**
	 * Select a train on the model.
	 * @param train the train to select.
	 */
	public void selectedTrain(Train train) {
		if(train == null) {
			return;
		}
		train.setSelected(true);
		fireTrainChanged(new TrainEvent(train,TrainEvent.TRAIN_SELECTED));
	}
	
	/**
	 * Unselect  train in the model.
 	 * @param train the train to unselect.
	 */
	public void unselectedTrain(Train train) {
		if(train == null) {
			return;
		}
		train.setSelected(false);
		fireTrainChanged(new TrainEvent(train,TrainEvent.TRAIN_UNSELECTED));
	}
	
////////////////////////////////////////////////////////////////////////////////
//////////////////////////////Methods of ItineraryModel/////////////////////////
////////////////////////////////////////////////////////////////////////////////


	@Override
	public Station getStart(Itinerary itinerary) {
		return itinerary.getStart();
	}


	@Override
	public Station getEnd(Itinerary itinerary) {
		return itinerary.getEnd();
	}	
	
	/**
	 * Adds a itinerary to a line.
	 * @param line the line where the itinerary is added.
	 * @param itinerary the itinerary to add.
	 */
	public void addItinerary(Line line, Itinerary itinerary) {
		if(line == null) {
			throw new NullPointerException("the line is null");
		}
		line.addItinaries(itinerary);
	}

	
	/**
	 * Starts the move of every trains of the model.
	 */
	public void start() {
		timer.start();
		fireStartApplication();
	}
	
	
	@Override
	public int getCountStation() {
		int numberOfStation=0;
		for(Line line : lines ) {
			numberOfStation += line.getCountStation();
		}
		return  numberOfStation;
	}
	
	
	public SortedMap<String, Station> getMapStation() {
		return  stations.tailMap("");
	}

}
